﻿//-----------------------------------------------------------------------------------------------------------
// WBFSSync Project by Omega Frost 
// http://wbfssync.codeplex.com/
//
// WBFSSync is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//------------------------------------------------------------------------------------------------------------
using System;
using System.IO;
using System.Linq;
using WBFSe3.Interfaces;
using WBFSe3.IO;
using WBFSe3.Native;
using WBFSe3.Streams;
using System.Runtime.InteropServices;

namespace WBFSe3.Wbfs
{
    //-------------------------------------------------------------------------------------------------------
    //
    //-------------------------------------------------------------------------------------------------------
    public class WbfsDevice : WbfsDriveInfo
    {
        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        private class UnmountedInfo : IWbfsGameInfo
        {
            public string name;
            public string code;
            public WbfsRegion region;
            public Boolean hasUpdate;
            public Boolean hasChannel;
            public Boolean isDL;
            public long length;

            public string Name { get { return this.name; } }
            public string Code { get { return this.code; } }
            public WbfsRegion Region { get { return this.region; } }
            public bool HasUpdate { get { return this.hasUpdate; } }
            public bool HasChannel { get { return this.hasChannel; } }
            public long Length { get { return this.length; } }
            public bool DoubleLayer { get { return this.isDL; } }
        }

        //---------------------------------------------------------------------------------------------------
        // Variables
        //---------------------------------------------------------------------------------------------------

        protected WbfsDeviceStream stream;

        protected Byte[] header;

        protected int discDescriptorSize;
        protected int maxDiscs;

        protected Byte[] sectorTable;
        protected int sectorTablePos;
        protected int sectorTableSize;

        protected int hdSectorSize;
        protected int hdSectorSizeLsh;
        protected int wbfsSectorSize;
        protected int wbfsSectorSizeLsh;

        protected int hdTotalSectors;
        protected int wbfsTotalSectors;
        protected int wiiTotalSectors;

        protected int wbfsSecsPerDisc;
        protected int wiiSecsPerWbfsSec;

        protected int wbfsUsedSectors;

        protected int numGames;
        protected String[] filenames;
        

        //---------------------------------------------------------------------------------------------------
        // Properties
        //---------------------------------------------------------------------------------------------------

        public int HdSectorSize
        {
            get { return this.hdSectorSize; }
        }

        public int WbfsSectorSize
        {
            get { return this.wbfsSectorSize; }
        }

        public int WbfsSecsPerDisc
        {
            get { return this.wbfsSecsPerDisc; }
        }

        public int DiscDescriptorSize
        {
            get { return this.discDescriptorSize; }
        }

        //---------------------------------------------------------------------------------------------------
        // Routines
        //---------------------------------------------------------------------------------------------------

        //---------------------------------------------------------------------------------------------------
        // Check for a wbfs partition and open it
        //---------------------------------------------------------------------------------------------------
        internal static WbfsError Open(String drive, FileAccess access, bool format, out WbfsDevice device)
        {
            device = null;
            WbfsError error;
            WbfsDevice dev = new WbfsDevice();
            WbfsDeviceStream stream = null;

            //Check for valid path
            if (String.IsNullOrEmpty(drive))
                return WbfsError.DriveNotFound;

            //Create the stream
            try
            {
                stream = new WbfsDeviceStream(drive, access);
                if (format) WbfsNative.LockVolume(stream.Handle);
            }
            catch (WbfsIOException exception)
            {
                error = (WbfsError)exception.NativeError;
                goto FAIL;
            }
            catch (FileNotFoundException)
            {
                error = WbfsError.IOFileNotFound;
                goto FAIL;
            }
            catch (DirectoryNotFoundException)
            {
                error = WbfsError.IODirNotFound;
                goto FAIL;
            }
            catch (UnauthorizedAccessException)
            {
                error = WbfsError.IOAccessDenied;
                goto FAIL;
            }
            catch (SharingViolationException)
            {
                error = WbfsError.IOSharingViolation;
                goto FAIL;
            }
            catch (Exception)
            {
                error = WbfsError.Fail;
                goto FAIL;
            }

            //Initialize the device class
            dev.stream = stream;
            dev.isWbfs = true;
            dev.largeFiles = true;
            dev.name = Char.ToUpper(drive[0]) + ":\\";
            dev.label = String.Empty;
            dev.serial = 0;
            dev.ParseType();

            dev.hdSectorSize = stream.SectorSize;
            dev.hdSectorSizeLsh = WbfsHelper.lshs(dev.hdSectorSize);

            dev.size = stream.Length;
            dev.hdTotalSectors = (int)(dev.size / dev.hdSectorSize);

            //Parse
            if ((error = dev.ParseHeader(format)) != WbfsError.Ok) goto FAIL;
            if ((error = dev.ParseSectorTable(format)) != WbfsError.Ok) goto FAIL;
            if ((error = dev.ParseGames()) != WbfsError.Ok) goto FAIL;
            dev.ParseSpace();

            if (format)
            {
                if ((error = dev.Flush()) != WbfsError.Ok)
                    goto FAIL;

                try
                {
                    WbfsNative.DismountVolume(stream.Handle);
                    WbfsNative.UnlockVolume(stream.Handle);       
                    stream.Close();

                    stream = new WbfsDeviceStream(drive, access);
                    dev.stream = stream;

                }
                catch (WbfsIOException exception)
                {
                    error = (WbfsError)exception.NativeError;
                    goto FAIL;
                }
                catch (FileNotFoundException)
                {
                    error = WbfsError.IOFileNotFound;
                    goto FAIL;
                }
                catch (DirectoryNotFoundException)
                {
                    error = WbfsError.IODirNotFound;
                    goto FAIL;
                }
                catch (UnauthorizedAccessException)
                {
                    error = WbfsError.IOAccessDenied;
                    goto FAIL;
                }
                catch (SharingViolationException)
                {
                    error = WbfsError.IOSharingViolation;
                    goto FAIL;
                }
                catch (Exception)
                {
                    error = WbfsError.Fail;
                    goto FAIL;
                }
            }

            dev.isReady = true;
            device = dev;
            return error;

        FAIL:

            stream.Close();
            return error;
        }


        //---------------------------------------------------------------------------------------------------
        // Open and format a device
        //---------------------------------------------------------------------------------------------------
        public static WbfsError Format(String drive, out WbfsDevice device)
        {
            WbfsDriveInfo di;
            WbfsError error;

            if (GetDrive(drive, out di) == WbfsError.Ok)
                di.Close();

            error = Open(drive, FileAccess.ReadWrite, true, out device);
            if (error != WbfsError.Ok) WbfsDriveInfo.InvalidateDrive(drive[0]);
            else WbfsDriveInfo.UpdateDrive(device);

            return error;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        private WbfsError ParseHeader(bool format)
        {
            WbfsError error;
            this.header = new byte[this.hdSectorSize];

            if ((error = Seek(0)) != WbfsError.Ok) return error;
            if ((error = Read(this.header, 0, this.hdSectorSize)) != WbfsError.Ok) return error;

            uint magic = WbfsHelper.ntohi(BitConverter.ToUInt32(this.header, WbfsConstants.WbfsHeadMagicPos));
            uint nsecs = WbfsHelper.ntohi(BitConverter.ToUInt32(this.header, WbfsConstants.WbfsHeadNSecsPos));
            byte hdsecsz = this.header[WbfsConstants.WbfsHeadHdSecSzPos];
            byte wbfssecsz = this.header[WbfsConstants.WbfsHeadWbfsSecSzPos];

            // Checks magic code
            if (!format && (magic != WbfsConstants.WbfsMagic))
                return WbfsError.DeviceMagicIncorrect;

            // Checks total sectors
            if (!format && (nsecs != this.hdTotalSectors))
                return WbfsError.DeviceSectorCountMismatch;

            // Checks physical sector size
            if (!format && (hdsecsz != this.hdSectorSizeLsh))
                return WbfsError.DeviceSectorSizeMismatch;

            // Calculate total wii sectors
            this.wiiTotalSectors = this.hdTotalSectors >>
                (WbfsConstants.WiiSectorSizeLSh - this.hdSectorSizeLsh);

            // Calculates the wbfs sector size
            this.wbfsSectorSizeLsh = CalculateWbfsSectorSize(this);
            this.wbfsSectorSize = 1 << this.wbfsSectorSizeLsh;

            // Checks wbfs sector size
            if (!format && (wbfssecsz != this.wbfsSectorSizeLsh))
                return WbfsError.DeviceWbfsSectorSizeMismatch;

            // Calculates the number of wbfs sectors per wii disc
            this.wbfsSecsPerDisc = WbfsHelper.WbfsSecsLshPerDisc(this.wbfsSectorSizeLsh);

            //Calculates the total wbfs sectors
            this.wbfsTotalSectors = this.hdTotalSectors >>
                (this.wbfsSectorSizeLsh - this.hdSectorSizeLsh);

            // Calculates the number of wii sectors per wbfs sector
            this.wiiSecsPerWbfsSec = 1 <<
                (this.wbfsSectorSizeLsh - WbfsConstants.WiiSectorSizeLSh);

            // Calculates the position and size of the sector usage table, it's
            // a bitfield at the end of the first wbfs partition that holds
            // the state of all wbfs sectors in the partition
            int nbytes = WbfsHelper.FitBit(this.wbfsTotalSectors, 8);

            this.sectorTablePos = WbfsHelper.Align(this.wbfsSectorSize -
               nbytes, this.hdSectorSize);

            this.sectorTableSize = WbfsHelper.Fit(nbytes, this.hdSectorSize);

            // Calculates the size of the disc entry in the filesystem's head, it's
            // composed by a slice of the game's header + the sectors were the game is
            this.discDescriptorSize = WbfsHelper.Fit(WbfsConstants.WbfsDiscHCopySz +
                this.wbfsSecsPerDisc * sizeof(ushort), this.hdSectorSize);

            // Calculates the maximum number of discs the fs can hold, 
            this.maxDiscs = Math.Min((this.sectorTablePos - this.hdSectorSize) /
                this.discDescriptorSize, this.hdSectorSize - 12);

            // If formatting rebuild the head
            if (format)
            {
                Byte[] header = new Byte[this.wbfsSectorSize];
                Array.Clear(header, 0, this.wbfsSectorSize);

                // Build the header
                Byte[] b = BitConverter.GetBytes(WbfsHelper.htoni(WbfsConstants.WbfsMagic));
                b.CopyTo(header, WbfsConstants.WbfsHeadMagicPos);

                b = BitConverter.GetBytes(WbfsHelper.htoni(this.hdTotalSectors));
                b.CopyTo(header, WbfsConstants.WbfsHeadNSecsPos);

                header[WbfsConstants.WbfsHeadHdSecSzPos] = (byte)this.hdSectorSizeLsh;
                header[WbfsConstants.WbfsHeadWbfsSecSzPos] = (byte)this.wbfsSectorSizeLsh;

                // Padding
                header[WbfsConstants.WbfsHeadWbfsSecSzPos + 1] = 0;
                header[WbfsConstants.WbfsHeadWbfsSecSzPos + 2] = 0;

                // Clear the disc usage table
                for (int i = 0; i < this.maxDiscs; i++)
                    header[WbfsConstants.WbfsHeadDiscTablePos + i] = 0;

                // Save the changes

                IntPtr pHeader = Marshal.AllocHGlobal(header.Length);
                Marshal.Copy(header, 0, pHeader, header.Length);

                int j = 0;
                for (int i = 0; i < wbfsSectorSize; i += stream.SectorSize)
                {
                    if ((error = WriteSector(j++, pHeader, i)) != WbfsError.Ok)
                    {
                        Marshal.FreeHGlobal(pHeader);
                        return error;
                    }
                }

                Marshal.FreeHGlobal(pHeader);
                Array.Copy(header, 0, this.header, 0, this.hdSectorSize);
            }

            this.filenames = new String[this.maxDiscs];
            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        private WbfsError ParseSectorTable(bool format)
        {
            WbfsError error;
            this.sectorTable = new byte[this.sectorTableSize];

            if (format)
            {
                for (int i = 0; i < this.sectorTableSize; i++)
                    this.sectorTable[i] = 0xFF;

                if ((error = Seek(this.sectorTablePos)) != WbfsError.Ok) return error;
                if ((error = Write(this.sectorTable, 0, this.sectorTableSize)) != WbfsError.Ok) return error;
            }
            else
            {
                if ((error = Seek(this.sectorTablePos)) != WbfsError.Ok) return error;
                if ((error = Read(this.sectorTable, 0, this.sectorTableSize)) != WbfsError.Ok) return error;

                // Convert network to host order
                byte temp;
                for (int i = 0; i < this.sectorTableSize; i += 4)
                {
                    // Outer bytes
                    temp = this.sectorTable[i];
                    this.sectorTable[i] = this.sectorTable[i + 3];
                    this.sectorTable[i + 3] = temp;

                    // Inner bytes
                    temp = this.sectorTable[i + 1];
                    this.sectorTable[i + 1] = this.sectorTable[i + 2];
                    this.sectorTable[i + 2] = temp;
                }
            }

            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected override int ParseSpace()
        {
            //Count used sectors
            int i, used = 0;
            byte val;

            // Iterate over bytes
            for (i = 0; i < this.sectorTableSize; i++)
            {
                if (this.sectorTable[i] == 0)
                {
                    used += 8;
                    continue;
                }

                // Iterate over bits
                val = this.sectorTable[i];
                for (int j = 0; j < 8; j++)
                {
                    if ((val & 0x1) == 0)
                        used++;

                    val >>= 1;
                }
            }

            //Calculate space
            this.wbfsUsedSectors = used;
            this.used = (long)this.wbfsSectorSize * used;
            this.size = (long)this.wbfsSectorSize * (this.wbfsTotalSectors - 1); //Not counting the header...
            this.free = this.size - this.used;

            return 0;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        private WbfsError ParseGames()
        {
            WbfsError error;
            Byte[] disc = new Byte[WbfsConstants.WbfsDiscHCopySz];
            this.numGames = 0;

            for (int i = 0; i < this.maxDiscs; i++)
            {
                if (this.header[WbfsConstants.WbfsHeadDiscTablePos + i] != 0)
                {
                    // Read the disc header copy
                    long p = this.hdSectorSize + i * this.discDescriptorSize;

                    if ((error = Seek(p)) == WbfsError.Ok)
                        error = Read(disc, 0, WbfsConstants.WbfsDiscHCopySz);

                    if (error != WbfsError.Ok) return error;

                    // Generate a filename
                    String name = String.Format("{0} {1}", 
                        WbfsHelper.ReadString(disc, WbfsConstants.WiiCodePos),
                        WbfsHelper.ReadString(disc, WbfsConstants.WiiNamePos));

                    this.filenames[i] = name;
                    this.numGames++;
                }
            }

            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        // Updates the drive
        //---------------------------------------------------------------------------------------------------
        public override WbfsError Update()
        {
            WbfsError error;

            lock (this)
            {
                Byte[] temp = new byte[this.hdSectorSize];

                // Check for formatting

                if ((error = Seek(0)) != WbfsError.Ok)
                    return error;

                if ((error = Read(temp, 0, this.hdSectorSize)) != WbfsError.Ok)
                    return error;

                uint magic = WbfsHelper.ntohi(BitConverter.ToUInt32(this.header, WbfsConstants.WbfsHeadMagicPos));
                uint nsecs = WbfsHelper.ntohi(BitConverter.ToUInt32(this.header, WbfsConstants.WbfsHeadNSecsPos));
                byte hdsecsz = this.header[WbfsConstants.WbfsHeadHdSecSzPos];
                byte wbfssecsz = this.header[WbfsConstants.WbfsHeadWbfsSecSzPos];

                //Checks magic code
                if (magic != WbfsConstants.WbfsMagic)
                    return WbfsError.DeviceMagicIncorrect;

                //Checks total sectors
                if (nsecs != this.hdTotalSectors)
                    return WbfsError.DeviceSectorCountMismatch;

                //Check physical sector size
                if (hdsecsz != this.hdSectorSizeLsh)
                    return WbfsError.DeviceSectorSizeMismatch;

                //Checks wbfs sector size
                if (wbfssecsz != this.wbfsSectorSizeLsh)
                    return WbfsError.DeviceWbfsSectorSizeMismatch;

                // Update filesystem header

                if ((error = Seek(0)) != WbfsError.Ok) 
                    return error;

                if ((error = Write(this.header, 0, this.hdSectorSize)) != WbfsError.Ok)
                    return error;


                // Update sector table

                Byte[] sectors = new Byte[this.sectorTableSize];

                for (int i = 0; i < this.sectorTableSize; i += 4)
                {
                    // Outer bytes

                    sectors[i] = this.sectorTable[i + 3];
                    sectors[i + 3] = this.sectorTable[i];

                    // Inner bytes
                    
                    sectors[i + 1] = this.sectorTable[i + 2];
                    sectors[i + 2] = this.sectorTable[i + 1];
                }

                if ((error = Seek(this.sectorTablePos)) != WbfsError.Ok)
                    return error;

                if ((error = Write(sectors, 0, this.sectorTableSize)) != WbfsError.Ok)
                    return error;

                ParseSpace();
                return WbfsError.Ok;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Calculates the wbfs sector size from the disk geometry
        //---------------------------------------------------------------------------------------------------
        private static byte CalculateWbfsSectorSize(WbfsDevice device)
        {
            Byte size_s;
            for (size_s = 6; size_s < 11; size_s++)
            {
                if (device.wiiTotalSectors < ((1U << 16) * (1 << size_s)))
                    break;
            }

            return (byte)(size_s + WbfsConstants.WiiSectorSizeLSh);
        }


        //---------------------------------------------------------------------------------------------------
        // Returns the game information stored in the partition header
        //---------------------------------------------------------------------------------------------------
        public WbfsError GetUnmountedGameInfos(out IWbfsGameInfo[] infos)
        {
            WbfsError error;

            int i = 0, j = 0;
            long p;

            infos = new IWbfsGameInfo[this.numGames];
            Byte[] array = new Byte[this.discDescriptorSize];

            lock (this)
            {
                while (j < this.numGames)
                {
                    if (this.header[WbfsConstants.WbfsHeadDiscTablePos + i] != 0)
                    {
                        p = this.hdSectorSize + i * 
                            this.discDescriptorSize;

                        if ((error = Seek(p)) == WbfsError.Ok)
                            error = Read(array, 0, this.discDescriptorSize);

                        if (error != WbfsError.Ok)
                        {
                            infos = null;
                            return error;
                        }

                        UnmountedInfo info = new UnmountedInfo();
                        info.code = WbfsHelper.ReadString(this.header, WbfsConstants.WiiCodePos, WbfsConstants.WiiCodeLen);
                        info.name = WbfsHelper.ReadString(this.header, WbfsConstants.WiiNamePos, WbfsConstants.WiiNameLen);
                        info.region = WbfsHelper.GetRegionFromCode(info.code);
                        info.hasUpdate = false;
                        info.hasChannel = false;
                        
                        info.length = 0;
                        info.isDL = false;
                        for (int k = 0; k < this.wbfsSecsPerDisc; k++)
                        {
                            if (WbfsHelper.ntohs(this.header, WbfsConstants.WbfsDiscHCopySz + 2 * k) != 0)
                            {
                                if (k > this.wbfsSecsPerDisc / 2)
                                    info.isDL = true;

                                info.length++;
                            }
                        }

                        info.length *= (long)this.wbfsSectorSize;
                        infos[j] = info;

                        j++;
                    }

                    i++;
                }
            }

            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        // Returns all the filenames in the partition
        //---------------------------------------------------------------------------------------------------
        public String[] GetFileNames()
        {
            var filenames = from f in this.filenames
                            where f != null
                            select f;

            return filenames.ToArray();
        }


        //---------------------------------------------------------------------------------------------------
        // Converts a filename to disc index
        //---------------------------------------------------------------------------------------------------
        public int GetDiscIndex(String filename)
        {
            lock (this)
            {
                for (int i = 0; i < filenames.Length; i++)
                {
                    if ((this.filenames[i] != null) && (String.Compare
                        (this.filenames[i], filename, true) == 0))
                        return i;
                }

                return -1;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Returns the filename of a specific disc index
        //---------------------------------------------------------------------------------------------------
        public String GetDiscFileName(int discidx)
        {
            return this.filenames[discidx];
        }


        //---------------------------------------------------------------------------------------------------
        // Reads a descriptor from a disc slot
        //---------------------------------------------------------------------------------------------------
        public WbfsError GetDiscDescriptor(out Byte[] array, int discidx)
        {
            WbfsError error;
            array = new Byte[this.discDescriptorSize];
            long p = this.hdSectorSize + discidx * this.discDescriptorSize;

            lock (this)
            {
                if ((error = Seek(p)) == WbfsError.Ok)
                    error = Read(array, 0, this.discDescriptorSize);

                return error;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Reads a sector table from a disc slot
        //---------------------------------------------------------------------------------------------------
        public WbfsError GetDiscSectorTable(out ushort[] table, int discidx)
        {
            WbfsError error;

            long p = this.hdSectorSize + WbfsConstants.WbfsDiscHCopySz +
                discidx * this.discDescriptorSize;

            int size = this.wbfsSecsPerDisc
                * sizeof(ushort);

            Byte[] array = new byte[size];
            table = null;

            // Read the sector table
            lock (this)
            {
                if ((error = Seek(p)) == WbfsError.Ok)
                    error = Read(array, 0, size);
            }

            if (error != WbfsError.Ok)
                return error;

            // Check for dual layer
            int count = this.wbfsSecsPerDisc / 2;
            for (int i = this.wbfsSecsPerDisc; i < 2 * this.wbfsSecsPerDisc; i++)
            {
                if (array[i] != 0)
                {
                    count *= 2;
                    break;
                }
            }

            //Build the sector table
            table = new ushort[count];

            for (int i = 0; i < count; i++)
            {
                ushort v = (ushort)WbfsHelper.ntohs(array, 2 * i);
                if (v != 0) table[i] = v;
            }

            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        // Reads the disc's header copy from a disc slot
        //---------------------------------------------------------------------------------------------------
        public WbfsError GetDiscHeaderCopy(out Byte[] array, int discidx)
        {
            WbfsError error;
            long p = this.hdSectorSize + discidx * this.discDescriptorSize;
            array = new byte[WbfsConstants.WbfsDiscHCopySz];

            lock (this)
            {
                if ((error = Seek(p)) == WbfsError.Ok)
                    error = Read(array, 0, WbfsConstants.WbfsDiscHCopySz);

            }
            return error;
        }


        //---------------------------------------------------------------------------------------------------
        // Writes a new descriptor to a disc slot
        //---------------------------------------------------------------------------------------------------
        public WbfsError SetDiscDescriptor(Byte[] array, int discidx)
        {
            WbfsError error;

            long p = this.hdSectorSize + discidx * this.discDescriptorSize;

            lock (this)
            {
                if ((error = Seek(p)) == WbfsError.Ok)
                    error = Write(array, 0, this.discDescriptorSize);
            }
            return error;
        }


        //---------------------------------------------------------------------------------------------------
        // Writes a new sector table to a disc slot
        //---------------------------------------------------------------------------------------------------
        public WbfsError SetDiscSectorTable(ushort[] table, int discidx)
        {
            WbfsError error;

            long p = this.hdSectorSize + WbfsConstants.WbfsDiscHCopySz +
                discidx * this.discDescriptorSize;

            int size = this.discDescriptorSize -
                WbfsConstants.WbfsDiscHCopySz;

            Byte[] array = new byte[size];

            for (int i = 0; i < table.Length; i++)
            {
                if (table[i] != 0)
                {
                    ushort v = (ushort)WbfsHelper.htons(table[i]);
                    BitConverter.GetBytes(v).CopyTo(array, 2 * i);
                }
            }

            lock (this)
            {
                if ((error = Seek(p)) == WbfsError.Ok)
                    error = Write(array, 0, size);
            }

            return error;
        }


        //---------------------------------------------------------------------------------------------------
        // Requests a disc slot
        //---------------------------------------------------------------------------------------------------
        public WbfsError RequestDiscSlot(out int discidx, String filename)
        {
            lock(this)
            {
                if (this.numGames == this.maxDiscs)
                {
                    discidx = -1;
                    return WbfsError.DeviceDiscTableFull;
                }

                for (int i = 0; i < this.maxDiscs; i++)
                {
                    if (this.header[WbfsConstants.WbfsHeadDiscTablePos + i] == 0)
                    {
                        this.header[WbfsConstants.WbfsHeadDiscTablePos + i] = 1;
                        this.filenames[i] = filename;
                        this.numGames++;
                        discidx = i;
                        return WbfsError.Ok;
                    }
                }
            }

            // Should not reach here...
            discidx = -1;
            return WbfsError.Fail;
        }


        //---------------------------------------------------------------------------------------------------
        // Requests sectors from the disk
        //---------------------------------------------------------------------------------------------------
        public WbfsError RequestSectors(out ushort[] sectors, int count)
        {
            lock (this)
            {
                int free = this.wbfsTotalSectors -
                    this.wbfsUsedSectors - 1;

                if (count > free)
                {
                    sectors = null;
                    return WbfsError.DeviceFull;
                }

                sectors = new ushort[count];
                this.wbfsUsedSectors += count;

                int i, k = 0;
                byte temp;

                // Iterate over bytes
                for (i = 0; i < this.sectorTableSize; i++)
                {
                    if (this.sectorTable[i] == 0)
                        continue;

                    // Iterate over bits
                    temp = this.sectorTable[i];
                    for (int j = 0; j < 8; j++)
                    {
                        if ((temp & 0x1) == 0)
                        {
                            temp >>= 1;
                            continue;
                        }

                        sectors[k++] = (ushort)
                            (1 + 8 * i + j);

                        this.sectorTable[i] &= (byte)
                            ~(0x1 << j);

                        if (k == count)
                            return WbfsError.Ok;

                        temp >>= 1;
                    }
                }
            }

            // Should not reach here...
            return WbfsError.Fail;
        }


        //---------------------------------------------------------------------------------------------------
        // Frees a disc slot
        //---------------------------------------------------------------------------------------------------
        public void FreeDiscSlot(int discidx)
        {
            lock (this)
            {
                this.header[WbfsConstants.WbfsHeadDiscTablePos + discidx] = 0;
                this.filenames[discidx] = null;
                this.numGames--;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Frees sectors from the disk
        //---------------------------------------------------------------------------------------------------
        public void FreeSectors(ushort[] sectors)
        {
            lock (this)
            {
                for (int i = 0; i < sectors.Length; i++)
                {
                    if (sectors[i] == 0)
                        continue;

                    int ibyte = (sectors[i] - 1) / 8;
                    int ibit = (sectors[i] - 1) % 8;

                    this.sectorTable[ibyte] |= (byte)
                        (0x1 << ibit);
                }
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Writes a new disc's header copy to a disc slot
        //---------------------------------------------------------------------------------------------------
        public WbfsError SetDiscHeaderCopy(Byte[] array, int discidx)
        {
            WbfsError error;
            long p = this.hdSectorSize + discidx * this.discDescriptorSize;

            lock (this)
            {
                if ((error = Seek(p)) == WbfsError.Ok)
                    error = Write(array, 0, WbfsConstants.WbfsDiscHCopySz);
            }
            return error;
        }


        //---------------------------------------------------------------------------------------------------
        // Reads data from the disk
        //---------------------------------------------------------------------------------------------------
        protected WbfsError Read(Byte[] array, int offset, int count)
        {
            try
            {
                this.stream.Read(array, offset, count);
                return WbfsError.Ok;
            }
            catch (WbfsIOException exception)
            {
                return (WbfsError)exception.NativeError;
            }
            catch (ObjectDisposedException)
            {
                return WbfsError.IODisposed;
            }
            catch (EndOfStreamException)
            {
                return WbfsError.IOReadError;
            }
            catch (NotSupportedException)
            {
                return WbfsError.IOReadError;
            }
            catch (Exception)
            {
                //ArgumentNullException
                //ArgumentOutOfRangeException
                //ArgumentException
                return WbfsError.Fail;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Reads data from the disk
        //---------------------------------------------------------------------------------------------------
        public WbfsError Read(Byte[] array, int offset, long position, int count)
        {
            WbfsError error;

            lock (this)
            {
                if ((error = Seek(position)) != WbfsError.Ok)
                    return error;

                return Read(array, offset, count);
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Reads data from the disk
        //---------------------------------------------------------------------------------------------------
        protected WbfsError ReadSector(int sector, IntPtr array, int offset)
        {
            try
            {
                this.stream.ReadSector(sector, array, offset);
                return WbfsError.Ok;
            }
            catch (WbfsIOException exception)
            {
                return (WbfsError)exception.NativeError;
            }
            catch (ObjectDisposedException)
            {
                return WbfsError.IODisposed;
            }
            catch (EndOfStreamException)
            {
                return WbfsError.IOReadError;
            }
            catch (NotSupportedException)
            {
                return WbfsError.IOReadError;
            }
            catch (Exception)
            {
                //ArgumentNullException
                //ArgumentOutOfRangeException
                //ArgumentException
                return WbfsError.Fail;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Writes data to the disk
        //---------------------------------------------------------------------------------------------------
        protected WbfsError Write(Byte[] array, int offset, int count)
        {
            try
            {
                this.stream.Write(array, offset, count);
                return WbfsError.Ok;
            }
            catch (WbfsIOException exception)
            {
                return (WbfsError)exception.NativeError;
            }
            catch (ObjectDisposedException)
            {
                return WbfsError.IODisposed;
            }
            catch (EndOfStreamException)
            {
                return WbfsError.IOWriteError;
            }
            catch (NotSupportedException)
            {
                return WbfsError.IOWriteError;
            }
            catch (Exception)
            {
                //ArgumentNullException
                //ArgumentOutOfRangeException
                //ArgumentException
                return WbfsError.Fail;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Writes data to the disk
        //---------------------------------------------------------------------------------------------------
        public WbfsError Write(Byte[] array, int offset, long position, int count)
        {
            WbfsError error;

            lock (this)
            {
                if ((error = Seek(position)) != WbfsError.Ok)
                    return error;

                return Write(array, offset, count);
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Writes data to the disk
        //---------------------------------------------------------------------------------------------------
        protected WbfsError WriteSector(int sector, IntPtr array, int offset)
        {
            try
            {
                this.stream.WriteSector(sector, array, offset);
                return WbfsError.Ok;
            }
            catch (WbfsIOException exception)
            {
                return (WbfsError)exception.NativeError;
            }
            catch (ObjectDisposedException)
            {
                return WbfsError.IODisposed;
            }
            catch (EndOfStreamException)
            {
                return WbfsError.IOWriteError;
            }
            catch (NotSupportedException)
            {
                return WbfsError.IOWriteError;
            }
            catch (Exception)
            {
                //ArgumentNullException
                //ArgumentOutOfRangeException
                //ArgumentException
                return WbfsError.Fail;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Sets the file pointer
        //---------------------------------------------------------------------------------------------------
        protected WbfsError Seek(long position)
        {
            try
            {
                this.stream.Seek(position, SeekOrigin.Begin);
                return WbfsError.Ok;
            }
            catch (WbfsIOException exception)
            {
                return (WbfsError)exception.NativeError;
            }
            catch (ObjectDisposedException)
            {
                return WbfsError.IODisposed;
            }
            catch (EndOfStreamException)
            {
                return WbfsError.IOReadError;
            }
            catch (NotSupportedException)
            {
                return WbfsError.IOSeekError;
            }
            catch (Exception)
            {
                //ArgumentNullException
                //ArgumentOutOfRangeException
                //ArgumentException
                return WbfsError.Fail;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Flush data
        //---------------------------------------------------------------------------------------------------
        protected WbfsError Flush()
        {
            try
            {
                this.stream.Flush();
                return WbfsError.Ok;
            }
            catch (WbfsIOException exception)
            {
                return (WbfsError)exception.NativeError;
            }
            catch (ObjectDisposedException)
            {
                return WbfsError.IODisposed;
            }
            catch (EndOfStreamException)
            {
                return WbfsError.IOReadError;
            }
            catch (NotSupportedException)
            {
                return WbfsError.IOReadError;
            }
            catch (Exception)
            {
                //ArgumentNullException
                //ArgumentOutOfRangeException
                //ArgumentException
                return WbfsError.Fail;
            }
        }


        //---------------------------------------------------------------------------------------------------
        // Closes the main stream
        //---------------------------------------------------------------------------------------------------
        public override WbfsError Close()
        {
            WbfsError error;

            lock (this)
            {
                if ((error = Update()) != WbfsError.Ok) return WbfsError.Ok;
                if ((error = Flush()) != WbfsError.Ok) return WbfsError.Ok;
                this.stream.Close();
            }

            return WbfsError.Ok;
        }
    }
}
